/*
	Thanks to
	https://github.com/XuShaohua/kwplayer/blob/master/kuwo/DES.py
	https://github.com/Levi233/MusicPlayer/blob/master/app/src/main/java/com/chenhao/musicplayer/utils/crypt/KuwoDES.java
*/

const Long =
  typeof BigInt === 'function' // BigInt support in Node 10+
    ? n => (
        (n = BigInt(n)),
        {
          low: Number(n),
          valueOf: () => n.valueOf(),
          toString: () => n.toString(),
          not: () => Long(~n),
          isNegative: () => n < 0,
          or: x => Long(n | BigInt(x)),
          and: x => Long(n & BigInt(x)),
          xor: x => Long(n ^ BigInt(x)),
          equals: x => n === BigInt(x),
          multiply: x => Long(n * BigInt(x)),
          shiftLeft: x => Long(n << BigInt(x)),
          shiftRight: x => Long(n >> BigInt(x))
        }
      )
    : (...args) => new (require('long'))(...args)

const range = n => Array.from(new Array(n).keys())
const power = (base, index) =>
  Array(index)
    .fill()
    .reduce(result => result.multiply(base), Long(1))
const LongArray = (...array) =>
  array.map(n => (n === -1 ? Long(-1, -1) : Long(n)))

// EXPANSION
const arrayE = LongArray(
  31,
  0,
  1,
  2,
  3,
  4,
  -1,
  -1,
  3,
  4,
  5,
  6,
  7,
  8,
  -1,
  -1,
  7,
  8,
  9,
  10,
  11,
  12,
  -1,
  -1,
  11,
  12,
  13,
  14,
  15,
  16,
  -1,
  -1,
  15,
  16,
  17,
  18,
  19,
  20,
  -1,
  -1,
  19,
  20,
  21,
  22,
  23,
  24,
  -1,
  -1,
  23,
  24,
  25,
  26,
  27,
  28,
  -1,
  -1,
  27,
  28,
  29,
  30,
  31,
  30,
  -1,
  -1
)

// INITIAL_PERMUTATION
const arrayIP = LongArray(
  57,
  49,
  41,
  33,
  25,
  17,
  9,
  1,
  59,
  51,
  43,
  35,
  27,
  19,
  11,
  3,
  61,
  53,
  45,
  37,
  29,
  21,
  13,
  5,
  63,
  55,
  47,
  39,
  31,
  23,
  15,
  7,
  56,
  48,
  40,
  32,
  24,
  16,
  8,
  0,
  58,
  50,
  42,
  34,
  26,
  18,
  10,
  2,
  60,
  52,
  44,
  36,
  28,
  20,
  12,
  4,
  62,
  54,
  46,
  38,
  30,
  22,
  14,
  6
)

// INVERSE_PERMUTATION
const arrayIP_1 = LongArray(
  39,
  7,
  47,
  15,
  55,
  23,
  63,
  31,
  38,
  6,
  46,
  14,
  54,
  22,
  62,
  30,
  37,
  5,
  45,
  13,
  53,
  21,
  61,
  29,
  36,
  4,
  44,
  12,
  52,
  20,
  60,
  28,
  35,
  3,
  43,
  11,
  51,
  19,
  59,
  27,
  34,
  2,
  42,
  10,
  50,
  18,
  58,
  26,
  33,
  1,
  41,
  9,
  49,
  17,
  57,
  25,
  32,
  0,
  40,
  8,
  48,
  16,
  56,
  24
)

// ROTATES
const arrayLs = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
const arrayLsMask = LongArray(0, 0x100001, 0x300003)
const arrayMask = range(64).map(n => power(2, n))
arrayMask[arrayMask.length - 1] = arrayMask[arrayMask.length - 1].multiply(-1)

// PERMUTATION
const arrayP = LongArray(
  15,
  6,
  19,
  20,
  28,
  11,
  27,
  16,
  0,
  14,
  22,
  25,
  4,
  17,
  30,
  9,
  1,
  7,
  23,
  13,
  31,
  26,
  2,
  8,
  18,
  12,
  29,
  5,
  21,
  10,
  3,
  24
)

// PERMUTED_CHOICE1
const arrayPC_1 = LongArray(
  56,
  48,
  40,
  32,
  24,
  16,
  8,
  0,
  57,
  49,
  41,
  33,
  25,
  17,
  9,
  1,
  58,
  50,
  42,
  34,
  26,
  18,
  10,
  2,
  59,
  51,
  43,
  35,
  62,
  54,
  46,
  38,
  30,
  22,
  14,
  6,
  61,
  53,
  45,
  37,
  29,
  21,
  13,
  5,
  60,
  52,
  44,
  36,
  28,
  20,
  12,
  4,
  27,
  19,
  11,
  3
)

// PERMUTED_CHOICE2
const arrayPC_2 = LongArray(
  13,
  16,
  10,
  23,
  0,
  4,
  -1,
  -1,
  2,
  27,
  14,
  5,
  20,
  9,
  -1,
  -1,
  22,
  18,
  11,
  3,
  25,
  7,
  -1,
  -1,
  15,
  6,
  26,
  19,
  12,
  1,
  -1,
  -1,
  40,
  51,
  30,
  36,
  46,
  54,
  -1,
  -1,
  29,
  39,
  50,
  44,
  32,
  47,
  -1,
  -1,
  43,
  48,
  38,
  55,
  33,
  52,
  -1,
  -1,
  45,
  41,
  49,
  35,
  28,
  31,
  -1,
  -1
)

const matrixNSBox = [
  [
    14,
    4,
    3,
    15,
    2,
    13,
    5,
    3,
    13,
    14,
    6,
    9,
    11,
    2,
    0,
    5,
    4,
    1,
    10,
    12,
    15,
    6,
    9,
    10,
    1,
    8,
    12,
    7,
    8,
    11,
    7,
    0,
    0,
    15,
    10,
    5,
    14,
    4,
    9,
    10,
    7,
    8,
    12,
    3,
    13,
    1,
    3,
    6,
    15,
    12,
    6,
    11,
    2,
    9,
    5,
    0,
    4,
    2,
    11,
    14,
    1,
    7,
    8,
    13
  ],
  [
    15,
    0,
    9,
    5,
    6,
    10,
    12,
    9,
    8,
    7,
    2,
    12,
    3,
    13,
    5,
    2,
    1,
    14,
    7,
    8,
    11,
    4,
    0,
    3,
    14,
    11,
    13,
    6,
    4,
    1,
    10,
    15,
    3,
    13,
    12,
    11,
    15,
    3,
    6,
    0,
    4,
    10,
    1,
    7,
    8,
    4,
    11,
    14,
    13,
    8,
    0,
    6,
    2,
    15,
    9,
    5,
    7,
    1,
    10,
    12,
    14,
    2,
    5,
    9
  ],
  [
    10,
    13,
    1,
    11,
    6,
    8,
    11,
    5,
    9,
    4,
    12,
    2,
    15,
    3,
    2,
    14,
    0,
    6,
    13,
    1,
    3,
    15,
    4,
    10,
    14,
    9,
    7,
    12,
    5,
    0,
    8,
    7,
    13,
    1,
    2,
    4,
    3,
    6,
    12,
    11,
    0,
    13,
    5,
    14,
    6,
    8,
    15,
    2,
    7,
    10,
    8,
    15,
    4,
    9,
    11,
    5,
    9,
    0,
    14,
    3,
    10,
    7,
    1,
    12
  ],
  [
    7,
    10,
    1,
    15,
    0,
    12,
    11,
    5,
    14,
    9,
    8,
    3,
    9,
    7,
    4,
    8,
    13,
    6,
    2,
    1,
    6,
    11,
    12,
    2,
    3,
    0,
    5,
    14,
    10,
    13,
    15,
    4,
    13,
    3,
    4,
    9,
    6,
    10,
    1,
    12,
    11,
    0,
    2,
    5,
    0,
    13,
    14,
    2,
    8,
    15,
    7,
    4,
    15,
    1,
    10,
    7,
    5,
    6,
    12,
    11,
    3,
    8,
    9,
    14
  ],
  [
    2,
    4,
    8,
    15,
    7,
    10,
    13,
    6,
    4,
    1,
    3,
    12,
    11,
    7,
    14,
    0,
    12,
    2,
    5,
    9,
    10,
    13,
    0,
    3,
    1,
    11,
    15,
    5,
    6,
    8,
    9,
    14,
    14,
    11,
    5,
    6,
    4,
    1,
    3,
    10,
    2,
    12,
    15,
    0,
    13,
    2,
    8,
    5,
    11,
    8,
    0,
    15,
    7,
    14,
    9,
    4,
    12,
    7,
    10,
    9,
    1,
    13,
    6,
    3
  ],
  [
    12,
    9,
    0,
    7,
    9,
    2,
    14,
    1,
    10,
    15,
    3,
    4,
    6,
    12,
    5,
    11,
    1,
    14,
    13,
    0,
    2,
    8,
    7,
    13,
    15,
    5,
    4,
    10,
    8,
    3,
    11,
    6,
    10,
    4,
    6,
    11,
    7,
    9,
    0,
    6,
    4,
    2,
    13,
    1,
    9,
    15,
    3,
    8,
    15,
    3,
    1,
    14,
    12,
    5,
    11,
    0,
    2,
    12,
    14,
    7,
    5,
    10,
    8,
    13
  ],
  [
    4,
    1,
    3,
    10,
    15,
    12,
    5,
    0,
    2,
    11,
    9,
    6,
    8,
    7,
    6,
    9,
    11,
    4,
    12,
    15,
    0,
    3,
    10,
    5,
    14,
    13,
    7,
    8,
    13,
    14,
    1,
    2,
    13,
    6,
    14,
    9,
    4,
    1,
    2,
    14,
    11,
    13,
    5,
    0,
    1,
    10,
    8,
    3,
    0,
    11,
    3,
    5,
    9,
    4,
    15,
    2,
    7,
    8,
    12,
    15,
    10,
    7,
    6,
    12
  ],
  [
    13,
    7,
    10,
    0,
    6,
    9,
    5,
    15,
    8,
    4,
    3,
    10,
    11,
    14,
    12,
    5,
    2,
    11,
    9,
    6,
    15,
    12,
    0,
    3,
    4,
    1,
    14,
    13,
    1,
    2,
    7,
    8,
    1,
    2,
    12,
    15,
    10,
    4,
    0,
    3,
    13,
    14,
    6,
    9,
    7,
    8,
    9,
    6,
    15,
    1,
    5,
    12,
    3,
    10,
    14,
    5,
    8,
    7,
    11,
    0,
    4,
    13,
    2,
    11
  ]
]

const bitTransform = (arrInt, n, l) => {
  // int[], int, long : long
  let l2 = Long(0)
  range(n).forEach(i => {
    if (arrInt[i].isNegative() || l.and(arrayMask[arrInt[i].low]).equals(0))
      return
    l2 = l2.or(arrayMask[i])
  })
  return l2
}

const DES64 = (longs, l) => {
  // long[], long
  let out = Long(0)
  let SOut = Long(0)
  const pR = range(8).map(() => Long(0))
  const pSource = [Long(0), Long(0)]
  let L = Long(0)
  let R = Long(0)
  out = bitTransform(arrayIP, 64, l)
  pSource[0] = out.and(0xffffffff)
  pSource[1] = out.and(-4294967296).shiftRight(32)

  range(16).forEach(i => {
    R = Long(pSource[1])
    R = bitTransform(arrayE, 64, R)
    R = R.xor(longs[i])
    range(8).forEach(j => {
      pR[j] = R.shiftRight(j * 8).and(255)
    })
    SOut = Long(0)
    range(8)
      .reverse()
      .forEach(sbi => {
        SOut = SOut.shiftLeft(4).or(matrixNSBox[sbi][pR[sbi]])
      })
    R = bitTransform(arrayP, 32, SOut)
    L = Long(pSource[0])
    pSource[0] = Long(pSource[1])
    pSource[1] = L.xor(R)
  })
  pSource.reverse()
  out = pSource[1]
    .shiftLeft(32)
    .and(-4294967296)
    .or(pSource[0].and(0xffffffff))
  out = bitTransform(arrayIP_1, 64, out)
  return out
}

const subKeys = (l, longs, n) => {
  // long, long[], int
  let l2 = bitTransform(arrayPC_1, 56, l)
  range(16).forEach(i => {
    l2 = l2
      .and(arrayLsMask[arrayLs[i]])
      .shiftLeft(28 - arrayLs[i])
      .or(l2.and(arrayLsMask[arrayLs[i]].not()).shiftRight(arrayLs[i]))
    longs[i] = bitTransform(arrayPC_2, 64, l2)
  })
  if (n === 1) {
    range(8).forEach(j => {
      ;[longs[j], longs[15 - j]] = [longs[15 - j], longs[j]]
    })
  }
}

const crypt = (msg, key, mode) => {
  // 处理密钥块
  let l = Long(0)
  range(8).forEach(i => {
    l = Long(key[i])
      .shiftLeft(i * 8)
      .or(l)
  })

  const j = Math.floor(msg.length / 8)
  // arrLong1 存放的是转换后的密钥块, 在解密时只需要把这个密钥块反转就行了

  const arrLong1 = range(16).map(() => Long(0))
  subKeys(l, arrLong1, mode)

  // arrLong2 存放的是前部分的明文
  const arrLong2 = range(j).map(() => Long(0))

  range(j).forEach(m => {
    range(8).forEach(n => {
      arrLong2[m] = Long(msg[n + m * 8])
        .shiftLeft(n * 8)
        .or(arrLong2[m])
    })
  })

  // 用于存放密文
  const arrLong3 = range(Math.floor((1 + 8 * (j + 1)) / 8)).map(() => Long(0))

  // 计算前部的数据块(除了最后一部分)
  range(j).forEach(i1 => {
    arrLong3[i1] = DES64(arrLong1, arrLong2[i1])
  })

  // 保存多出来的字节
  const arrByte1 = msg.slice(j * 8)
  let l2 = Long(0)

  range(msg.length % 8).forEach(i1 => {
    l2 = Long(arrByte1[i1])
      .shiftLeft(i1 * 8)
      .or(l2)
  })

  // 计算多出的那一位(最后一位)
  if (arrByte1.length || mode === 0) arrLong3[j] = DES64(arrLong1, l2) // 解密不需要

  // 将密文转为字节型
  const arrByte2 = range(8 * arrLong3.length).map(() => 0)
  let i4 = 0
  arrLong3.forEach(l3 => {
    range(8).forEach(i6 => {
      arrByte2[i4] = l3.shiftRight(i6 * 8).and(255).low
      i4 += 1
    })
  })
  return Buffer.from(arrByte2)
}

const SECRET_KEY = Buffer.from('ylzsxkwm')
const encrypt = msg => crypt(msg, SECRET_KEY, 0)
const decrypt = msg => crypt(msg, SECRET_KEY, 1)
const encryptQuery = query => encrypt(Buffer.from(query)).toString('base64')

module.exports = { encrypt, decrypt, encryptQuery }
